/**
 * Project: Utilities <br>
 * Package: org.leumasjaffe.collections.map
 */
package org.leumasjaffe.collections.map;

import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NavigableMap;
import java.util.NavigableSet;
import java.util.NoSuchElementException;
import java.util.Set;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;

/**
 * Class org.leumasjaffe.collections.map..AbstractNavigableMultiMap in file 
 * AbstractNavigableMultiMap.java <br>
 * 
 * @author leumasjaffe
 * @version Feb 1, 2014
 * Created on: Feb 1, 2014
 * 
 */
@NonNullByDefault
public class DelegateNavigableMultiMap<K, V> extends
		DelegateSortedMultiMap<K, V> implements NavigableMultiMap<K, V> {

	private final NavigableMap<K, List<V>> navigableInternalMap;
	
	protected class NavigableSubMultiMap extends DelegateNavigableMultiMap<K, V> {
		private int size = -1;
		private transient int expetedModCount = DelegateNavigableMultiMap.this.modCount;
		
		/**
		 * @param internalMap
		 */
		protected NavigableSubMultiMap(NavigableMap<K, List<V>> internalMap) {
			super(internalMap);
		}
		
		/* (non-Javadoc)
		 * @see org.leumasjaffe.collections.map.AbstractMultiMap#size()
		 */
		@Override
		public int size() {
			recalcSize();
			return this.size;
		}

		/**
		 * 
		 */
		private void recalcSize() {
			if (this.size == -1 || 
					this.expetedModCount != DelegateNavigableMultiMap.this.modCount) {
				this.size = 0;
				for (List<V> list : DelegateNavigableMultiMap.this.navigableInternalMap.values()) {
					this.size += list.size();
				}
				this.expetedModCount = DelegateNavigableMultiMap.this.modCount;
			}
		}
		
	}
	
	protected class DescendingMultiMap extends DelegateNavigableMultiMap<K, V> {

		protected class DescendingEntrySet extends MultiEntrySet {
			@Override
			@SuppressWarnings("null")
			public Iterator<Entry<K, V>> iterator() {
				return new DescendingEntryIterator(super.internalEntrySet.iterator());
			}
		}
		
		protected class DescendingEntryIterator implements Iterator<Entry<K, V>> {
			protected Iterator<Entry<K, Collection<V>>> internalIterator;
			protected @Nullable K nextKey = null;
			@SuppressWarnings("null")
			protected ListIterator<V> nextIt = Collections.emptyListIterator();
			protected int index = 0;
			
			DescendingEntryIterator(Iterator<Entry<K, Collection<V>>> iterator) {
				this.internalIterator = iterator;
				advance();
			}

			/**
			 * 
			 */
			@SuppressWarnings("null")
			private void advance() {
				while (!this.nextIt.hasPrevious() && this.internalIterator.hasNext()) {
					final Entry<K, Collection<V>> entry = this.internalIterator.next();
					this.nextKey = entry.getKey();
					this.index = entry.getValue().size();
					if (entry.getValue() != null) {
						this.nextIt = ((List<V>) entry.getValue()).listIterator(this.index);
					} else {
						this.nextIt = Collections.emptyListIterator();
					}
				}
			}

			/* (non-Javadoc)
			 * @see java.util.Iterator#hasNext()
			 */
			@Override
			public boolean hasNext() {
				return this.nextIt.hasPrevious();
			}

			/* (non-Javadoc)
			 * @see java.util.Iterator#next()
			 */
			@Override
			public Entry<K, V> next() {
				if (!hasNext()) {
					throw new NoSuchElementException();
				}
				Entry<K, V> entry = new MultiEntry(this.nextKey, this.nextIt.previous(), --this.index);
				if (!hasNext()) {
					advance();
				}
				return entry;
			}

			/* (non-Javadoc)
			 * @see java.util.Iterator#remove()
			 */
			@Override
			public void remove() {
				this.nextIt.remove();
			}
		}
		
		/**
		 * @param internalMap
		 */
		@SuppressWarnings("null")
		public DescendingMultiMap() {
			super(DelegateNavigableMultiMap.this.navigableInternalMap.descendingMap());
		}
		
		@Override
		public Set<Entry<K, V>> entrySet() {
			return new DescendingEntrySet();
		}
	}
	
	/**
	 * @param internalMap
	 */
	public DelegateNavigableMultiMap(NavigableMap<K, List<V>> internalMap) {
		super(internalMap);
		this.navigableInternalMap = internalMap;
	}

	/* (non-Javadoc)
	 * @see java.util.NavigableMap#lowerEntry(java.lang.Object)
	 */
	@Override
	public @Nullable Entry<K, V> lowerEntry(@Nullable K key) {
		final Entry<K, List<V>> entry = this.navigableInternalMap.lowerEntry(key);
		final List<V> data = entry.getValue();
		return new MultiEntry(entry.getKey(), data.get(data.size() - 1), data.size() - 1);
	}

	/* (non-Javadoc)
	 * @see java.util.NavigableMap#lowerKey(java.lang.Object)
	 */
	@Override
	public @Nullable K lowerKey(@Nullable K key) {
		return this.navigableInternalMap.lowerKey(key);
	}

	/* (non-Javadoc)
	 * @see java.util.NavigableMap#floorEntry(java.lang.Object)
	 */
	@Override
	public @Nullable Entry<K, V> floorEntry(@Nullable K key) {
		final Entry<K, List<V>> entry = this.navigableInternalMap.floorEntry(key);
		final List<V> data = entry.getValue();
		return new MultiEntry(entry.getKey(), data.get(data.size() - 1), data.size() - 1);
	}

	/* (non-Javadoc)
	 * @see java.util.NavigableMap#floorKey(java.lang.Object)
	 */
	@Override
	public @Nullable K floorKey(@Nullable K key) {
		return this.navigableInternalMap.floorKey(key);
	}

	/* (non-Javadoc)
	 * @see java.util.NavigableMap#ceilingEntry(java.lang.Object)
	 */
	@Override
	public @Nullable Entry<K, V> ceilingEntry(@Nullable K key) {
		final Entry<K, List<V>> entry = this.navigableInternalMap.ceilingEntry(key);
		final List<V> data = entry.getValue();
		return new MultiEntry(entry.getKey(), data.get(0), 0);
	}

	/* (non-Javadoc)
	 * @see java.util.NavigableMap#ceilingKey(java.lang.Object)
	 */
	@Override
	public @Nullable K ceilingKey(@Nullable K key) {
		return this.navigableInternalMap.ceilingKey(key);
	}

	/* (non-Javadoc)
	 * @see java.util.NavigableMap#higherEntry(java.lang.Object)
	 */
	@Override
	public @Nullable Entry<K, V> higherEntry(@Nullable K key) {
		final Entry<K, List<V>> entry = this.navigableInternalMap.higherEntry(key);
		final List<V> data = entry.getValue();
		return new MultiEntry(entry.getKey(), data.get(0), 0);
	}

	/* (non-Javadoc)
	 * @see java.util.NavigableMap#higherKey(java.lang.Object)
	 */
	@Override
	public @Nullable K higherKey(@Nullable K key) {
		return this.navigableInternalMap.higherKey(key);
	}

	/* (non-Javadoc)
	 * @see java.util.NavigableMap#firstEntry()
	 */
	@Override
	public @Nullable Entry<K, V> firstEntry() {
		final Entry<K, List<V>> entry = this.navigableInternalMap.firstEntry();
		final List<V> data = entry.getValue();
		return new MultiEntry(entry.getKey(), data.get(0), 0);
	}

	/* (non-Javadoc)
	 * @see java.util.NavigableMap#lastEntry()
	 */
	@Override
	public @Nullable Entry<K, V> lastEntry() {
		final Entry<K, List<V>> entry = this.navigableInternalMap.lastEntry();
		final List<V> data = entry.getValue();
		return new MultiEntry(entry.getKey(), data.get(data.size() - 1), data.size() - 1);
	}

	/* (non-Javadoc)
	 * @see java.util.NavigableMap#pollFirstEntry()
	 */
	@Override
	public @Nullable Entry<K, V> pollFirstEntry() {
		final Entry<K, List<V>> entry = this.navigableInternalMap.firstEntry();
		final List<V> data = entry.getValue();
		return new SimpleImmutableEntry<K, V>(entry.getKey(), data.remove(0));
	}

	/* (non-Javadoc)
	 * @see java.util.NavigableMap#pollLastEntry()
	 */
	@Override
	public @Nullable Entry<K, V> pollLastEntry() {
		final Entry<K, List<V>> entry = this.navigableInternalMap.lastEntry();
		final List<V> data = entry.getValue();
		return new SimpleImmutableEntry<K, V>(entry.getKey(), data.remove(data.size() - 1));
	}

	/* (non-Javadoc)
	 * @see java.util.NavigableMap#descendingMap()
	 */
	@Override
	public NavigableMap<K, V> descendingMap() {
		return new DescendingMultiMap();
	}

	/* (non-Javadoc)
	 * @see java.util.NavigableMap#navigableKeySet()
	 */
	@SuppressWarnings("null")
	@Override
	public NavigableSet<K> navigableKeySet() {
		return this.navigableInternalMap.navigableKeySet();
	}

	/* (non-Javadoc)
	 * @see java.util.NavigableMap#descendingKeySet()
	 */
	@SuppressWarnings("null")
	@Override
	public NavigableSet<K> descendingKeySet() {
		return this.navigableInternalMap.descendingKeySet();
	}

	/* (non-Javadoc)
	 * @see java.util.NavigableMap#subMap(java.lang.Object, boolean, java.lang.Object, boolean)
	 */
	@SuppressWarnings("null")
	@Override
	public NavigableMap<K, V> subMap(@Nullable K fromKey, boolean fromInclusive, 
			@Nullable K toKey, boolean toInclusive) {
		return new NavigableSubMultiMap(this.navigableInternalMap.subMap(
				fromKey, fromInclusive, toKey, toInclusive));
	}

	/* (non-Javadoc)
	 * @see java.util.NavigableMap#headMap(java.lang.Object, boolean)
	 */
	@SuppressWarnings("null")
	@Override
	public NavigableMap<K, V> headMap(@Nullable K toKey, boolean inclusive) {
		return new NavigableSubMultiMap(this.navigableInternalMap.headMap(toKey, inclusive));
	}

	/* (non-Javadoc)
	 * @see java.util.NavigableMap#tailMap(java.lang.Object, boolean)
	 */
	@SuppressWarnings("null")
	@Override
	public NavigableMap<K, V> tailMap(@Nullable K fromKey, boolean inclusive) {
		return new NavigableSubMultiMap(this.navigableInternalMap.tailMap(fromKey, inclusive));
	}

}
